1 module targets.psvita;
2 import std.exception;
3 import features.git;
4 import commons;
5 import std.path;
6 
7 private enum updateCmd = "sudo apt-get update";
8 private enum depsInstallCmd = "sudo apt-get install make git-core cmake python3 curl wget bzip2";
9 private enum vdpmRepo = "https://github.com/vitasdk/vdpm";
10 private enum bootstrapVsdk = "./bootstrap-vitasdk.sh";
11 private enum installAllVsdk = "./install-all.sh";
12 
13 version(Posix)
14     private string vitaSdkPath="/usr/local/vitasdk";
15 else
16     private string vitaSdkPath;
17 
18 private enum vitasdkExports =
19     "\n"~"export PATH=$VITASDK/bin:$PATH #adds vitasdk tool to $PATH";
20 
21 private enum vdpmInstallCmd =
22     "git clone https://github.com/vitasdk/vdpm && "~
23     "cd vdpm && "~
24     "./bootstrap-vitasdk-.sh "~
25     "./install-all.sh";
26 
27 
28 private bool setupPsvitaLinux(ref Terminal t, ref RealTimeConsoleInput input)
29 {
30     string exports = "\n"~"export VITASDK="~vitaSdkPath ~ vitasdkExports;
31     std.file.append(buildPath(environment["HOME"], ".bashrc"), exports);
32     t.wait(spawnShell(updateCmd));
33     t.wait(spawnShell(depsInstallCmd));
34     t.wait(spawnShell(vdpmInstallCmd));
35     t.wait(spawnShell("vitasdk-update"));
36     return true;
37 }
38 private string getWslSource()
39 {
40     return executeShell("wsl echo -n $(wslpath \"%USERPROFILE%\")/.bashrc").output;
41 }
42 
43 private string getWslPath(string inputPath)
44 {
45     import std.path;
46     import std.string:replace, toLower;
47     import std.conv;
48     return "/mnt/"~driveName(inputPath)[0].toLower.to!string~replace(inputPath[2..$], '\\', '/');
49 }
50 
51 private auto getExecFunc(ref Terminal t)
52 {
53     return (scope string arg)
54     {
55         version(Windows)
56         {
57             string toExec = "wsl source "~getWslSource~" ^&^& "~arg;
58             t.writelnHighlighted("Executing: ", toExec);
59             return wait(spawnShell(toExec));
60         }
61         else
62         {
63             t.writelnHighlighted("Executing: ", arg);
64             return wait(spawnShell(arg));
65         }
66     };
67 }
68 
69 /** 
70  * The first build will simply generate the VPK, by mapping assets, compiling
71  * all the required stuff for PSVita, and also including the main binary (eboot.bin)
72  * after the first build is done, one must manually install the hipreme_engine.vpk
73  * by using the vitashell utility.
74  * Params:
75  *   t = 
76  * Returns: 
77  */
78 private bool firstBuild(ref Terminal t)
79 {
80     with(WorkingDir(getHipPath("build", "vita", "hipreme_engine")))
81     {
82         t.flush;
83         auto exec = getExecFunc(t);
84         if(exec("make") != 0)
85         {
86             t.writelnError("Make failed.");
87             return false;
88         }
89         if(exec("curl ftp://"~configs["psvIp"].str~":1337/ux0:/ -T ./hipreme_engine.vpk") != 0)
90         {
91             t.writelnError("Could not send the VPK.");
92             return false;
93         }
94         return true;
95     }
96 }
97 
98 /** 
99  * Instead of building the entire VPK for PS Vita, it only changes the binary, after that,
100  * it directly sends this new binary to the Package folder, so, there will be no need
101  * to extract a VPK by going into the Install Process again, making it a lot faster
102  * to both build and test.
103  *
104  *  For even faster installation, it is recomended to run a background FTP on PSV
105  * Params:
106  *   t = 
107  * Returns: 
108  */
109 private bool fastBuild(ref Terminal t)
110 {
111     enum APP_ID = "VSDK00007";
112     auto exec = getExecFunc(t);
113 
114     with(WorkingDir(getHipPath("build", "vita", "hipreme_engine")))
115     {
116         version(Windows) enum pipe = "^|";
117         else enum pipe = "|";
118 
119         // exec("make clean");
120         if(exec("make eboot.bin") != 0)
121         {
122             t.writelnError("Could not rebuild.");
123             return false;
124         }
125         // exec("echo screen on "~pipe~" nc "~configs["psvIp"].str~" "~configs["psvCmdPort"].str);
126         if(exec("curl ftp://"~configs["psvIp"].str~":1337/ux0:/app/"~APP_ID~"/ -T ./eboot.bin") != 0)
127         {
128             t.writelnError("Could not send eboot.bin");
129             return false;
130         }
131         // exec("echo launch "~APP_ID~" "~pipe~" nc "~configs["psvIp"].str~" "~configs["psvCmdPort"].str);
132         return true;
133     }
134 }
135 
136 private bool setupPsvitaWindows(ref Terminal t, ref RealTimeConsoleInput input)
137 {
138     if(executeShell("where wsl").status != 0)
139     {
140         t.writelnError("Please, run a command prompt with administrator access and run `wsl --install` before developing for PSV on Windows.");
141         return false;
142     }
143     string vitaPath = getHipPath("tools", "hbuild", "PSVita");
144 
145 
146     string bashRc = buildPath(environment["USERPROFILE"], ".bashrc");
147     string fileToSource = getWslSource();
148 
149     vitaSdkPath = getWslPath(buildPath(vitaPath, "vitasdk"));
150 
151 
152 
153     string exports = "\n"~"export VITASDK="~vitaSdkPath~vitasdkExports;
154     if(std.file.exists(bashRc))
155     {
156         import std.algorithm.searching:countUntil;
157         string data = std.file.readText(bashRc);
158         if(countUntil(data, "VITASDK") == -1)
159             std.file.append(bashRc, exports);
160     }
161     else std.file.append(bashRc, exports);
162 
163     auto wslExec = (scope string[] commands...)
164     {
165         import std.array:join;
166         t.writelnHighlighted("WSL Execution: "~commands);
167         return t.wait(spawnShell("wsl source "~fileToSource~" ^&^& "~join(commands, " ")));
168     };
169     
170     if(wslExec(updateCmd) != 0)
171     {
172         t.writelnError("Could not update system repositores");
173         return false;
174     }
175     if(wslExec(depsInstallCmd) != 0)
176     {
177         t.writelnError("Could not setup vita dependencies");
178         return false;
179     }
180     with(WorkingDir(vitaPath))
181     {
182         if(!std.file.exists("vdpm"))
183         {
184             if(wslExec("git clone "~vdpmRepo) != 0)
185             {
186                 t.writelnError("Could not clone vdpm");
187                 return false;
188             }
189         }
190     }
191     with(WorkingDir(buildPath(vitaPath, "vdpm")))
192     {
193         if(wslExec("which vitasdk-update") != 0 && wslExec(bootstrapVsdk) != 0)
194         {
195             t.writelnError("Could not execute "~bootstrapVsdk);
196             wslExec("sudo rm -rf "~vitaSdkPath);
197             return false;
198         }
199         if(wslExec(installAllVsdk) != 0)
200         {
201             t.writelnError("Could not execute "~installAllVsdk);
202             return false;
203         }
204     }
205     if(wslExec("vitasdk-update") != 0)
206     {
207         t.writelnError("Could not update vitasdk");
208         return false;
209     }
210     return true;
211 }
212 
213 bool setupPsvita(ref Terminal t, ref RealTimeConsoleInput input)
214 {
215      if(!extractToFolder(
216         getHipPath("build", "vita", "hipreme_engine", "hipreme_engine_vita_dev_files.7z"),
217         getHipPath("build", "vita", "hipreme_engine"),
218         t, input
219     ))
220     {
221         t.writelnError("PSVita requires 7zip to extract the development files.");
222         return false;
223     }
224     //https://vitasdk.org/
225 
226     bool ret;
227     version(Windows) ret = setupPsvitaWindows(t, input);
228     else version(linux) ret = setupPsvitaLinux(t, input);
229     else assert(false, "Not supported");
230 
231     if(ret)
232     {
233         configs["vitaSdkPath"] = buildPath(vitaSdkPath, "vitasdk");
234         configs["firstPsvConfig"] = true;
235         updateConfigFile();
236     }
237     return ret;
238 
239 }
240 
241 ChoiceResult preparePSVita(Choice* c, ref Terminal t, ref RealTimeConsoleInput input, in CompilationOptions cOpts)
242 {
243     if(!("firstPsvConfig" in configs) || !configs["firstPsvConfig"].boolean)
244     {
245         if(!setupPsvita(t, input))
246             return ChoiceResult.Error;
247     }
248 	cached(() => timed(t, submoduleLoader.execute(t, input)));    
249     import std.string;
250 	executeGameRelease(t);
251     putResourcesIn(t, getHipPath("build", "vita", "hipreme_engine", "assets"));
252 
253     string dflags = "-I="~configs["hipremeEnginePath"].str~"/modules/d_std/source "~
254     "-I="~configs["hipremeEnginePath"].str~"/dependencies/runtime/druntime/source "~
255     "-d-version=PSVita " ~
256     "-d-version=PSV " ~
257     "--revert=dtorfields "~
258     "-mcpu=cortex-a9 "~
259     "-mattr=+neon,+neonfp,+thumb-mode "~
260     // "-Os " ~
261     "-fvisibility=hidden "~
262     "-float-abi=hard "~
263     "-gcc="~buildNormalizedPath(configs["vitaSdkPath"].str, "bin", "arm-vita-eabi-gcc")~" "~
264     "--relocation-model=static "~
265     "-d-version=CarelessAlocation "~
266     "-d-version=ArsdUseCustomRuntime ";
267 
268     environment["DFLAGS"] = dflags;
269 
270     requireConfiguration("psvIp", "Set up PSVita IP for installing your application via FTP.", t, input, (ref str)
271     {
272         str = str.strip();
273         return isIpAddress(str);
274     }, "psvIp must be a valid IP address");
275 
276 
277     requireConfiguration("psvCmdPort", "Set up PSVita Command Port for automatic execution after compilation+installation.", t, input, (ref str)
278     {
279         str = str.strip();
280         return isNumeric(str);
281     });
282 
283 
284     with(WorkingDir(configs["gamePath"].str))
285     {
286         ProjectDetails d;
287         if(waitRedub(t, DubArguments().command("build").configuration("psvita").arch("armv7a-unknown-unknown-eabi").opts(cOpts), d,
288         getHipPath("build", "vita", "hipreme_engine", "libs")) != 0)
289         {
290             t.writelnError("Could not build for PSVita.");
291             return ChoiceResult.Error;
292         }
293 
294         static bool isFirstBuild = true;
295         if(isFirstBuild)
296         {
297             if(!firstBuild(t))
298             {
299                 t.writelnError("Could not build PSVita hipreme_engine.vpk");
300                 return ChoiceResult.Error;
301             }
302             isFirstBuild = false;
303         }
304         else
305         {
306             if(!fastBuild(t))
307             {
308                 t.writelnError("Could not do subsequent builds.");
309                 return ChoiceResult.Error;
310             }
311         }
312     }
313 
314     return ChoiceResult.None;
315 }